import { computed, reactive, ref } from 'vue';

export interface Position {
    x: number,
    y: number
}

export interface UseSwipeOptions {
    /**
     * @default 50
     */
    threshold?: number

    /**
     * Callback on swipe start.
     */
    onSwipeStart?: (e: PointerEvent) => void

    /**
     * Callback on swipe move.
     */
    onSwipe?: (e: PointerEvent) => void

    /**
     * Callback on swipe end.
     */
    onSwipeEnd?: (e: PointerEvent, direction: 'left'|'right'|'up'|'down'|'none', duration?: number) => void

    /**
     * Pointer types to listen to.
     *
     * @default ['mouse', 'touch', 'pen']
     */
    pointerTypes?: any[]
}

export function useSwipe (target: any, options: UseSwipeOptions ) {
    const {
        threshold = 50,
        onSwipe,
        onSwipeEnd,
        onSwipeStart,
    } = options;

    const posStart = reactive<Position>({ x: 0, y: 0 });
    const updatePosStart = (x: number, y: number) => {
        posStart.x = x;
        posStart.y = y;
    };

    const posEnd = reactive<Position>({ x: 0, y: 0 });
    const updatePosEnd = (x: number, y: number) => {
        posEnd.x = x;
        posEnd.y = y;
    };

    const distanceX = computed(() => posStart.x - posEnd.x);
    const distanceY = computed(() => posStart.y - posEnd.y);

    const { max, abs } = Math;
    const isThresholdExceeded = computed(() => max(abs(distanceX.value), abs(distanceY.value)) >= threshold);
    const isSwiping = ref(false);
    const isPointerDown = ref(false);
    const duration = ref(0);
    let startT: DOMHighResTimeStamp;

    const direction = computed(() => {
        if (!isThresholdExceeded.value) return 'none';

        if (abs(distanceX.value) > abs(distanceY.value)) {
            return distanceX.value > 0 ? 'left' : 'right';
        }
        return distanceY.value > 0 ? 'up' : 'down';
    });

    const eventIsAllowed = (e: PointerEvent): boolean => {
        const isReleasingButton = e.buttons === 0;
        const isPrimaryButton = e.buttons === 1;
        return (
            options.pointerTypes?.includes(e.pointerType) ?? (isReleasingButton || isPrimaryButton) ?? true
        );
    };

    const handlePointerDown = (e: PointerEvent) => {
        if (!eventIsAllowed(e)) return;
        e.preventDefault();
        isPointerDown.value = true;
        startT = performance.now();

        // Disable scroll on for TouchEvents
        target?.style?.setProperty('touch-action', 'none');
        // Future pointer events will be retargeted to target until pointerup/cancel
        const eventTarget = e.target as HTMLElement | undefined;
        eventTarget?.setPointerCapture(e.pointerId);
        const { clientX: x, clientY: y } = e;
        updatePosStart(x, y);
        updatePosEnd(x, y);
        onSwipeStart?.(e);
    };

    const handlePointerMove = (e: PointerEvent) => {
        if (!eventIsAllowed(e)) return;
        if (!isPointerDown.value) return;

        const { clientX: x, clientY: y } = e;
        updatePosEnd(x, y);
        if (!isSwiping.value && isThresholdExceeded.value) isSwiping.value = true;
        if (isSwiping.value) onSwipe?.(e);
    };

    const handlePointerUp = (e: PointerEvent) => {
        if (!eventIsAllowed(e)) return;
        const durationVal = performance.now() - startT;
        duration.value = durationVal;

        if (isSwiping.value) onSwipeEnd?.(e, direction.value, durationVal);

        isPointerDown.value = false;
        isSwiping.value = false;
        target?.style?.setProperty('touch-action', 'initial');
    };

    target.addEventListener('pointerdown', handlePointerDown);
    target.addEventListener('pointermove', handlePointerMove);
    target.addEventListener('pointerup', handlePointerUp);

    const stop = () => {
        target.removeEventListener('pointerdown', handlePointerDown);
        target.removeEventListener('pointermove', handlePointerMove);
        target.removeEventListener('pointerup', handlePointerUp);
    };

    return {
        isSwiping,
        direction,
        posStart,
        posEnd,
        distanceX,
        distanceY,
        duration,
        stop
    };
}
